package com.ouc.pampas;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class DubboHelper
{
  private static final Logger log = LoggerFactory.getLogger(DubboHelper.class);
  private static final String DEFAULT_VERSION = "DEFAULT";
  @Autowired
  private ApplicationContext applicationContext;
  private LoadingCache<ReferenceKey, Optional<ReferenceObj>> referenceCache;
  private LoadingCache<ReferenceKey, ServiceBean<?>> providerCache;
  
  @PostConstruct
  private void init()
  {
	  this.referenceCache = CacheBuilder.newBuilder().build(new CacheLoader()
	    {
	      public Optional<DubboHelper.ReferenceObj> load(DubboHelper.ReferenceKey key)
	        throws Exception
	      {
	        ReferenceBean<Object> referenceBean = new ReferenceBean();
	        referenceBean.setApplicationContext(DubboHelper.this.applicationContext);
	        referenceBean.setInterface(key.clazz);
	        if ((!Strings.isNullOrEmpty(key.version)) && (!Objects.equal(key.version, "DEFAULT"))) {
	          referenceBean.setVersion(key.version);
	        }
	        if (!Strings.isNullOrEmpty(key.url)) {
	          referenceBean.setUrl(key.url);
	        }
	        try
	        {
	          referenceBean.afterPropertiesSet();
	          return Optional.of(new DubboHelper.ReferenceObj(referenceBean, referenceBean.get()));
	        }
	        catch (Exception e)
	        {
	          DubboHelper.log.error("error when init dubbo reference bean. class {}, version {}", new Object[] { key.clazz, key.version, e });
	        }
	        return Optional.absent();
	      }

		@Override
		public Object load(Object key) throws Exception {
			// TODO 自动生成的方法存根
			return null;
		}
	    });
	    this.providerCache = CacheBuilder.newBuilder().build(new CacheLoader()
	    {
	      public ServiceBean<?> load(DubboHelper.ReferenceKey key)
	        throws Exception
	      {
	        ServiceBean<Object> serviceBean = new ServiceBean();
	        serviceBean.setApplicationContext(DubboHelper.this.applicationContext);
	        serviceBean.setInterface(key.clazz);
	        serviceBean.setRef(DubboHelper.this.applicationContext.getBean(key.clazz));
	        if ((!Strings.isNullOrEmpty(key.version)) && (!Objects.equal(key.version, "DEFAULT"))) {
	          serviceBean.setVersion(key.version);
	        }
	        serviceBean.afterPropertiesSet();
	        return serviceBean;
	      }

		@Override
		public Object load(Object key) throws Exception {
			// TODO 自动生成的方法存根
			return null;
		}
	    });
   
  }
  
  @PreDestroy
  private void destroy()
  {
    for (Optional<ReferenceObj> objOptional : this.referenceCache.asMap().values()) {
      if (objOptional.isPresent()) {
        ((ReferenceObj)objOptional.get()).referenceBean.destroy();
      }
    }
    for (ServiceBean serviceBean : this.providerCache.asMap().values()) {
      try
      {
        serviceBean.destroy();
      }
      catch (Exception e)
      {
        log.warn("error when destroy dubbo serviceBean", e);
      }
    }
  }
  
  public <T> T getReference(Class<T> clazz, String version)
  {
    return getReference(new ReferenceKey(clazz, version));
  }
  
  public <T> T getReference(Class<T> clazz, String version, String ip, Integer port)
  {
    port = (Integer)MoreObjects.firstNonNull(port, Integer.valueOf(20880));
    String url = "dubbo://" + ip + ":" + port + "/" + clazz.getName();
    ReferenceKey key = new ReferenceKey(clazz, version);
    key.url = url;
    return getReference(key);
  }
  
  public <T> void exportProvider(Class<T> clazz, String version)
  {
    this.providerCache.getUnchecked(new ReferenceKey(clazz, version));
  }
  
  private <T> T getReference(ReferenceKey key)
  {
    Optional<ReferenceObj> referenceOptional = (Optional)this.referenceCache.getUnchecked(key);
    if (referenceOptional.isPresent()) {
      return (T) ((ReferenceObj)referenceOptional.get()).bean;
    }
    return null;
  }
  
  private static class ReferenceKey
  {
    private Class clazz;
    private String version = "DEFAULT";
    private String url;
    
    private ReferenceKey(Class clazz, String version)
    {
      this.clazz = clazz;
      this.version = version;
    }
    
    public boolean equals(Object o)
    {
      if (this == o) {
        return true;
      }
      if (!(o instanceof ReferenceKey)) {
        return false;
      }
      ReferenceKey that = (ReferenceKey)o;
      if (!this.clazz.equals(that.clazz)) {
        return false;
      }
      if (this.url != null ? !this.url.equals(that.url) : that.url != null) {
        return false;
      }
      if (!this.version.equals(that.version)) {
        return false;
      }
      return true;
    }
    
    public int hashCode()
    {
      int result = this.clazz.hashCode();
      result = 31 * result + this.version.hashCode();
      result = 31 * result + (this.url != null ? this.url.hashCode() : 0);
      return result;
    }
  }
  
  private static class ReferenceObj
  {
    private ReferenceBean<?> referenceBean;
    private Object bean;
    
    private ReferenceObj(ReferenceBean<?> referenceBean, Object bean)
    {
      this.referenceBean = referenceBean;
      this.bean = bean;
    }
  }
}
